<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>SVG</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
          integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <script src="scripts/snap.svg.js"></script>
    <style>

        .title {
            font-size: 32px;
            font-family: "Andale Mono";
        }
        .sub_title {
            font-size: 28px;
            font-family: "Andale Mono";
        }
        .eden {
            fill: #fff6e9;
            stroke: gray;
            stroke-width: 5;
        }
        .survivor {
            fill: #d2e7ff;
            stroke: gray;
            stroke-width: 5;
        }
        .young {
            fill: #b5580d;
            stroke: yellow;
            stroke-width: 1;
        }
        .old {
            fill: #e4dcf1;
            stroke: gray;
            stroke-width: 5;
        }
        .addEden{
            stroke-width: 2;
            stroke: #000;
            marker-end: url(#markerArrow)
        }

        #debug_alert {
            font-size: 32px;
        }


    </style>
</head>

<body>
<div id="main-content" class="container">

    <div class="row">
        <div class="col-md-12">
            <div id="debug_alert" class="alert alert-success text-center" role="alert">
                JVM GC 年轻代的标记复制算法 动画演示
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <div class="float-md-right">
<!--                <button id="run" type="button" class="btn btn-primary">运行</button>-->
<!--                <button id="debug" type="button" class="btn btn-warning">调试</button>-->
<!--                <button id="step" type="button" class="btn btn-success">下一步</button>-->
<!--                <button id="pause" type="button" class="btn btn-secondary">暂停</button>-->
<!--                <button id="stop" type="button" class="btn btn-secondary">停止</button>-->

                <button id="go" type="button" class="btn btn-info">继续</button>
                <button id="mark" type="button" class="btn btn-secondary">标记</button>
                <button id="copy" type="button" class="btn btn-secondary">移动</button>
                <button id="swap" type="button" class="btn btn-secondary">清除</button>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <svg id="jvm" width="1200" height="1400">
                <defs>
                    <marker id="markerArrow" markerWidth="10" markerHeight="7"
                            refX="0" refY="3.5"  orient="auto">
                        <polygon points="0 0, 10 3.5, 0 7" />
                    </marker>
                </defs>
                <g id="heap">
                    <g id="new" transform="translate(600 50)">
                        <ellipse rx="50" ry="30" fill="yellow" stroke="blue" stroke-width="3"/>
                        <text x="0" y="5" font-size="28" text-anchor="middle">new</text>
                    </g>
                    <g id="young" transform="translate(200 150)"/>
                    <g id="old" transform="translate(200 620)"/>
                </g>
            </svg>
        </div>
    </div>

</div>

</svg>
    <script>

        let young_g = Snap('#young');

       gc_config = {
            SurvivorRatio: 8, // survivor:eden = 1:8
            NewRatio: 2 // young:old = 1:2
        }
         obj_space = {
            width: 80,
            height: 40
        }
         eden_space = {
            width: 800,
            height: 320,
        }
         survivor_space = {
            width: eden_space.width / 1,
            height: eden_space.height / 8
        }
        old_space = {
            width: eden_space.width,
            height: eden_space.height * gc_config.NewRatio
        }
        let colors = [
            "#a8e6cf", "#dcedc1", "#ffd3b6", "#ffaaa5",
            "#ff3377", "#ee4035", "#f37736", "#fdf498",
            "#7bc043", "#0392cf", "#8595a1", "#58668b",
        ]


        young_g.rect(-10, -10, eden_space.width + 20, eden_space.height + survivor_space.height * 2 + 60)
            .addClass('young');
        young_g.text(-120, 10, "年轻代").addClass('title');
        young_g.text(-120, 50, "Young").addClass('title');

        let eden_g = young_g.g().attr({id: 'eden', transform: "translate(0 0)"});
        eden_g.rect(0, 0, eden_space.width, eden_space.height).addClass('eden');
        eden_g.text(-120, 160, "伊甸区").addClass('sub_title');
        eden_g.text(-120, 200, "Eden").addClass('sub_title');

        let s0_g = young_g.g().attr({id: 's0', transform: `translate(0 ${eden_space.height + 20})`});
        s0_g.rect(0, 0, survivor_space.width, survivor_space.height).addClass('survivor');
        s0_g.text(-140, 30, "存活区0").addClass('sub_title');
        s0_g.text(-180, 30, "S0").addClass('sub_title');

        let s1_g = young_g.g().attr({id: 's1', transform: `translate(0 ${eden_space.height + 80})`});
        s1_g.rect(0, 0, survivor_space.width, survivor_space.height).addClass('survivor');
        s1_g.text(-140, 30, "存活区1").addClass('sub_title');
        s1_g.text(-180, 30, "S1").addClass('sub_title');

        let old_g = Snap('#old');
        old_g.rect(0, 0, old_space.width, old_space.height).addClass('old');
        old_g.text(-120, 100, "老年代").addClass('title');
        old_g.text(-120, 160, "Old").addClass('title');


        let new_dot = Snap('#heap');
        let new_dot_circle = new_dot.circle(600, 80, 20).attr(
            {
                // stroke: "red",
                // "stroke-width": 5,
                fill: "red"
            }
        );
        new_dot.path("M 600 80 L 600 120").addClass('addEden');
        function newEden(){
            new_dot_circle.attr({cx:600,cy:80});
            new_dot_circle.animate({cx:600,cy:130},100,mina.linear);
        }
        let j=1;
        let i=0;
        let edenObjs=new Array();
        function addEden(){
            let eden = Snap('#eden');
            let obj_pointer = i * obj_space.width;
            let x_offset = obj_pointer % eden_space.width;
            let y_offset = Math.floor(obj_pointer / eden_space.width) * obj_space.height;
            let obj_id =j;
            let obj_width = 1 * obj_space.width;


            if(obj_pointer>79*obj_space.width){
                // sweep("eden");
                clearInterval(intervalId);
                clearInterval(newEdenId);
                return;
            }
            let ob=new Object();
            ob.id=j;
            ob.address=j;
            ob.age=0;
            edenObjs.push(ob);
            let obj_g = eden.g().attr({
                id: `obj-${obj_id}`,
                transform: `translate(${x_offset}, ${y_offset})`
            });
            obj_g.rect(0, 0, obj_width, obj_space.height)
                .attr({
                    fill: colors[Math.floor(Math.random() * 12)],
                    stroke: "gray",
                    "stroke-width": 1,
                })
                .addClass("obj_new");
            obj_g.text(obj_width / 2, obj_space.height / 2 + 5, j)
                .attr({'text-anchor': 'middle', "font-size": 20});
            i++;
            j++

        }
        function mark (obj) {
            let obj_id = obj.id;
            let obj_g = Snap('#obj-'+obj_id);
            obj_g.select('rect').animate({fill: 'white',stroke: 'red'},
                2000, mina.bounce,
                () => {
                    obj_g.select('rect').animate(
                        {width: obj_space.width, height: obj_space.height},
                        2000, mina.bounce);
                });
            Snap.animate(0, 1, function (value) {
                obj_g.select('text').attr({'font-size': 50, opacity: value});
            }, 2000, mina.bounce);
        }
        function copy(c) {
            // let from = c.from.toLowerCase();
            console.log("current epoch:"+epoch);
            let to = c.to.toLowerCase();
            let obj_id = c.id;
            let address =c.address;
            let offset_x = address * obj_space.width;
            let offset_y = 1;
            let obj_pointer = address * obj_space.width;
            offset_x = obj_pointer % eden_space.width;
            offset_y = Math.floor(obj_pointer / eden_space.width) * obj_space.height;
            let obj_g = Snap(`#obj-${obj_id}`).clone().attr({id: `obj-${obj_id}`});
            Snap(`#${to}`).add(obj_g);
            obj_g.animate(
                {
                    transform: `translate(${offset_x} ${offset_y})`
                },
                1000, mina.linear);
            obj_g.select('text').attr({"font-size": 20, "text": `${obj_id},${c.age}`});
        }
        function  sweep(s) {
            let space = s.toLowerCase();
            Snap(`#${space}`).selectAll('g').remove();
        }

        let s1Obj=[];
        let s0Obj=[];
        let oldObj=[];
        let liveObj=[];
        let epoch="s1";//epoch s1
        document.getElementById("copy").onclick=clickCopy;
        function clickCopy () {
            if(markobjs.length!=0){

                let obj = markobjs[0];
                obj.to=epoch;
                obj.age=obj.age+1;
                obj.address=epoch==='s1' ? s1Obj.length:s0Obj.length;
                if(obj.age>=3){
                    obj.to='old';
                    obj.address=oldObj.length;
                    oldObj.push(obj);
                    copy(obj);
                    markobjs.shift();

                    return false;
                }
                copy(obj);
                getArrayByEpoch().push(obj);
                markobjs.shift();

            }

            return true;
        }

        let markobjs=[];
        document.getElementById("mark").onclick=clickMark;
        function clickMark() {

            for(let k=0;k<10;k++){

                // obj.id=Math.floor(Math.random() * 79);
                if(k===0 && liveObj!=null&&liveObj.length>0){
                    // let index =Math.floor(Math.random() * liveObj.length);
                    let index =0;
                    let edenObj = liveObj[index];
                    // edenObj.age =edenObj.age+1;
                    // liveObj[index]=edenObj;
                    markobjs.push(edenObj);
                    continue;
                }
                if(edenObjs!=null&&edenObjs.length>0){
                    let index =Math.floor(Math.random() * (edenObjs.length));
                    let edenObj = edenObjs[index];
                    // if(edenObj!=null &&edenObj.age){
                    //     edenObj.age =edenObj.age+1;
                    // }
                    // edenObjs[index]=edenObj;
                    if(edenObj==null ||  edenObj.id==null ||!Object.hasOwn(edenObj, 'id')||edenObj.id==='undefined'){
                        edenObjs.splice(index,1);
                        k--;
                        continue;
                    }

                    let obj=new Object();

                    obj.id=edenObj.id;
                    obj.address=edenObj.address;
                    obj.age=edenObj.age;
                    markobjs.push(obj);
                }

            }
            markobjs.forEach(function (obj) {
                mark(obj);
            })
            return true;

        }
        function clickSwap(){
            sweep("eden");
            sweep(getEpoch());
            let cur =getArrayByEpoch();

            liveObj = cur.splice(0,cur.length);
            return true;
        }
        document.getElementById("swap").onclick=clickSwap;

        // document.getElementById("stop").onclick=function () {
        //     clearInterval(intervalId);
        // };
        function clickGo(){
            i=0;
            edenObjs=[];
            // edenObjs.splice(0,edenObjs.length);
            newEdenId = setInterval(newEden,100);
            intervalId= setInterval(addEden,10);
            epoch=getEpoch();
            return true;
        }
        document.getElementById("go").onclick=clickGo;
        function getEpoch(){
            if(epoch === 's1'){
                return 's0';
            }else if(epoch === 's0'){
                return  's1';
            }
        }
        let newEdenId = setInterval(newEden,100);
        let intervalId= setInterval(addEden,10);

        setInterval(join,3000);
        function join() {
            console.log("开始执行clickMark")
            clearInterval(intervalId);
            clearInterval(newEdenId);
            sleep(2000).then(()=>{
                clickMark();
            }).then(()=>{
                sleep(1000).then(()=>{
                    console.log(markobjs);
                    let count =markobjs.length;
                    for(let h=0;h<count;h++){
                        console.log("开始执行clickCopy")
                        clickCopy();
                    }
                }).then(()=>{
                    console.log("开始执行clickSwap")
                    clickSwap();
                }).then(()=>{
                    console.log("开始执行clickGo")
                    clickGo();
                });

            })
        }
        function sleep(wait) {
            return new Promise((res, rej) => {
                setTimeout(() => {
                    this.num = 1;
                    res(wait);
                }, wait);
            });
        }
        function deepClone(oldData, target){
            for(let i in oldData){
                let item=oldData[i]
                if(item instanceof Object){
                    target[i]={}
                    deepClone(item, target[i])  // //递归调用deepClone，一个个拷贝
                }else if(item instanceof Array){
                    target[i]=[]
                    deepClone(item, target[i])
                }else{
                    target[i]=item
                }
            }
            return target
        }


        function getArrayByEpoch(){
            if(epoch==='s0'){
                return s0Obj;
            }else {
                return s1Obj;
            }
        }






        // let s = Snap('#jvm');
        // let offset = 50;
        // let g = s.g().attr({ stroke: 'gray' });
        // for (let i = 0; i <=1600 / offset; i++) {
        //     g.line(i * offset, 0, i * offset, 1600)
        //         .attr({ "stroke-width": '1', "stroke-dasharray": "5, 5" });
        //     g.line(0, i * offset, 1600, i * offset)
        //         .attr({ "stroke-width": '1', "stroke-dasharray": "5, 5" });
        //     g.text(0, i * offset, i * offset);
        //     g.text(i * offset, 20, i * offset);
        // }
    </script>
</body>

</html>